    precision mediump float;
    precision mediump int;
    uniform mat4 uMVMatrix; // model-view matrix (model -> view space)
    uniform mat4 uPMatrix; // projection matrix (view -> projection space)
    uniform mat4 uNMatrix; // normal matrix (inverse of transpose of model-view matrix)

    uniform mat4 uLightMatrix; // mvpdMatrix of light space (model -> display space)

    attribute vec3 aVertexNormal;
    attribute vec2 aTextureCoord;
    attribute float aVertexEdge; // 0 or 1. 1 if the vertex has an edge. (becuase we can't pass bool to attributes)

    attribute float aWeightType;//0=BDEF1 1=BDEF2 2 = BDEF4 3 = SDEF 4 = QDEF

    attribute float aBoneWeight1;
    attribute float aBoneWeight2;
    attribute float aBoneWeight3;
    attribute float aBoneWeight4;

    attribute vec3 aVectorFromBone1;
    attribute vec3 aVectorFromBone2;
    attribute vec3 aVectorFromBone3;
    attribute vec3 aVectorFromBone4;

    attribute vec4 aBone1Rotation;
    attribute vec4 aBone2Rotation;
    attribute vec4 aBone3Rotation;
    attribute vec4 aBone4Rotation;

    attribute vec3 aBone1Position;
    attribute vec3 aBone2Position;
    attribute vec3 aBone3Position;
    attribute vec3 aBone4Position;


    attribute vec3 aMultiPurposeVector;

    varying vec3 vPosition;
    varying vec3 vNormal;
    varying vec2 vTextureCoord;
    varying vec4 vLightCoord; // coordinate in light space; to be mapped onto shadow map

    uniform float uEdgeThickness;
    uniform int uEdge;

    uniform int uGenerateShadowMap;

    uniform int uSelfShadow;

    uniform int uAxis;
    uniform int uCenterPoint;

    vec3 qtransform(vec4 q, vec3 v) {
        return v + 2.0 * cross(cross(v, q.xyz) - q.w*v, q.xyz);
    }
    vec3 calculateWeight(vec3 p1, float w1,vec3 p2, float w2, vec3 p3, float w3, vec3 p4, float w4){
        return (p1*w1+p2*w2+p3*w3+p4*w3)/(w1+w2+w3+w4);
    }

    void main() {
        vec3 position;
        vec3 normal;

        if (uAxis==1 || uCenterPoint==1) {
            position = aMultiPurposeVector;

        } else {
            vec3 morph = aMultiPurposeVector;
            if(aWeightType == 1.0 || aWeightType == 3.0){
                position = qtransform(aBone1Rotation, aVectorFromBone1 + morph) + aBone1Position;
                normal = qtransform(aBone1Rotation, aVertexNormal);
                vec3 p2 = qtransform(aBone2Rotation, aVectorFromBone2 + morph) + aBone2Position;
                vec3 n2 = qtransform(aBone2Rotation, aVertexNormal);
                position = mix(p2, position, aBoneWeight1);
                normal = normalize(mix(n2, normal, aBoneWeight1));

            }else if(aWeightType == 2.0 || aWeightType == 4.0){
                vec3 p1 = qtransform(aBone1Rotation, aVectorFromBone1 + morph) + aBone1Position;
                vec3 n1 = qtransform(aBone1Rotation, aVertexNormal);
                vec3 p2 = qtransform(aBone2Rotation, aVectorFromBone2 + morph) + aBone2Position;
                vec3 n2 = qtransform(aBone2Rotation, aVertexNormal);
                vec3 p3 = qtransform(aBone3Rotation, aVectorFromBone3 + morph) + aBone3Position;
                vec3 n3 = qtransform(aBone3Rotation, aVertexNormal);
                vec3 p4 = qtransform(aBone4Rotation, aVectorFromBone4 + morph) + aBone4Position;
                vec3 n4 = qtransform(aBone4Rotation, aVertexNormal);
                position = calculateWeight(p1, aBoneWeight1, p2, aBoneWeight2, p3, aBoneWeight3, p4, aBoneWeight4);
                normal = normalize(calculateWeight(n1, aBoneWeight1, n2, aBoneWeight2, n3, aBoneWeight3, n4, aBoneWeight4));

            }else{
                position = qtransform(aBone1Rotation, aVectorFromBone1 + morph) + aBone1Position;
                normal = qtransform(aBone1Rotation, aVertexNormal);

            }


        }

        // return vertex point in projection space
        gl_Position = uPMatrix * uMVMatrix * vec4(position, 1.0);

        if (uCenterPoint==1) {
            gl_Position.z = 0.0; // always on top
            gl_PointSize = 16.0;
        }

        if (uGenerateShadowMap==1 || uAxis==1 || uCenterPoint==1) return;

        // for fragment shader
        vTextureCoord = aTextureCoord;
        vPosition = (uMVMatrix * vec4(position, 1.0)).xyz;
        vNormal = (uNMatrix * vec4(normal, 1.0)).xyz;

        if (uSelfShadow==1) {
            vLightCoord = uLightMatrix * vec4(position, 1.0);
        }

        if (uEdge==1) {
            vec4 pos = gl_Position;
            vec4 pos2 = uPMatrix * uMVMatrix * vec4(position + normal, 1.0);
            vec4 norm = normalize(pos2 - pos);
            gl_Position = pos + norm * uEdgeThickness * aVertexEdge * pos.w; // scale by pos.w to prevent becoming thicker when zoomed
            return;
        }
    }

